import bpy, os
from lxml import etree
from . import collada_funcs
from . import blender_funcs
from . import math_funcs

# Comments (spanish) (reescribir):

# Bind Pose o Pose de Vínculo (aplica a huesos): es la posición (transformación) que tienen los huesos de un modelo en 3D cuando asignas los huesos a las mallas de dicho modelo. Cualquier otra posición deforma las mallas del modelo. A las matrices que llevan esta información se les denomina matriz de vínculo o bind matrix. En los archivos collada las matrices de vínculo (inversas) de los huesos nombrados en el elemento <Name_array> pueden obtenerse del elemento <float_array> (que viene después de <Name_array>) en el elemento <skin> de dicho archivo. Hay que invertir las matrices en esta sección para obtener las que se van a palicar a los huesos. Estas transformaciones son con respecto al sistema del objeto armadura (no son locales!).

# Bind Shape Matrix o Matriz Forma de Vínculo (aplica a mallas): es la matriz de transformación que se le aplica a las mallas del modelo en 3D para que dichas mallas esten en la posición correcta para ser usadas por los huesos de dicho modelo. De otra forma, las mallas tienen una posición (transformación) original y estas matrices la modifican para entonces tener los vertices de dicha malla en la posición deseada para ser movidos por los huesos del modelo. Esta posición original creo que no tiene que ver con el proceso de vínculo (huesos con la malla) o con la pose de descanso, creo que es algo separado a ambos.

# Rest Pose o Pose de Descanso (aplica a huesos): o pose de descanso, es la posición (transformación) asignada a los huesos luego de enlazar (vincular) los huesos a la malla del modelo en 3D. Por su descripción, puede ser igual o diferente a las transformaciones de hueso en la pose de vínculo. Es la transformación que se usa como base para las animaciones de esqueleto (no se usan para describir la posición de los huesos con las mallas!!!). Estas transformaciones son con respecto al origen del hueso padre (no son locales!).

# Voy a suponer que las mallas con su matriz de forma de vinculo estan ya en la configuración para la pose de vinculo con los huesos (¿será por eso que se llama bind shape matrix?)

# lo lógico es entonces:
# - Cargar las mallas con el importador de collada de blender (Mallas con su BSM)
# - Poner los huesos en su posición de vínculo con la información en las mallas (Huesos en su pose para empezar a deformar las mallas).
# - Crear una pose de descanso para el esqueleto en el modo pose de blender (Huesos en su pose para animar las mallas)
# - Almacenar la informacion de las poses de vínculo y descanso en las propieades de los huesos del esqueleto de blender

# import_collada_superbmd() function
# read superbmd/blenxy collada file
# used to import the collada file into Blender
def import_collada_superbmd(context, filepath):
  
  # scene variable is always needed for something
  scene = bpy.context.scene
  
  # make a temporal collada file with the "unit" and "up_axis" elements modified
  # and import that file (elements inside the assets element)
  # also fix the dumb Z:\ paths that happen when using wine
  xml = collada_funcs.check_collada(filepath)
  if (xml == None):
    blender_funcs.disp_msg("Collada file isn't well formatted")
    return {'FINISHED'} 
  
  # get asset's unit element
  root = xml.getroot()

  # texture files path
  if (os.name == "posix"):
    for image in root.find("library_images"):
      tmp = image.find("init_from").text
      while (tmp[0] != '\\'):
        tmp = tmp[1:]
      image.find("init_from").text = tmp.replace("\\", "/")
  
  # save the new collada file and load it into blender
  xml.write(filepath + ".xml", pretty_print = True)
  bpy.ops.wm.collada_import(filepath = filepath + ".xml")
  # remove the temporal file
  os.remove(filepath + ".xml")
  
  ########################################
  # reconstruct the skeleton for bind pose
  # get the armature object and start doing math shidge
  armature = bpy.data.objects[-1] # last object imported  
  
  # apply transform and select only the object
  blender_funcs.select_obj(scene, armature, False)
  
  # Showtime (bind pose, meshes are already in their correct position)
  
  # delete all bones in the armature
  bpy.ops.object.mode_set(mode = 'EDIT')
  bpy.ops.armature.select_all(action = 'SELECT')
  bpy.ops.armature.delete()
  
  # get the bones bind matrices
  # traverse through the <Name_array> and 
  # <float_array> elements with the bone bind pose data
  bind_matrices = {}
  for controller in root.find("library_controllers").findall("controller"):
    bone_names = controller[0][1][0].text.split()
    bone_matrices = controller[0][2][0].text
    for i in range(0, len(bone_names)):
      mat = collada_funcs.get_text_mat4x4(bone_matrices, 16 * i).inverted()
      dict_entry = {bone_names[i] : mat}
      bind_matrices.update(dict_entry)
  
  # get the bones rest matrices and in parallel reconstruct the armature
  # traverse through the <node> elements in <visual_scene>
  rest_matrices = {}  
  # search for the node named "skeleton_root" inside the <visual_scene>
  # element which is inside the <library_visual_scenes> element
  jnt_root_node = root.find("library_visual_scenes").find("visual_scene")
  for node in jnt_root_node:
    if (node.attrib["name"] == "skeleton_root"):
      jnt_root_node = node.find("node")
      break  
  
  # get the bone rest pose information
  bpy.ops.object.mode_set(mode='EDIT')
  jnts_same_level = [jnt_root_node]
  is_first_jnt = True
  while (jnts_same_level != []):
    
    # get the joints for the next iteration
    next_jnts = []    
    for jnt in jnts_same_level:
      for elem in jnt.getchildren():
        if (elem.tag == "node"):
          next_jnts.append(elem)
    
    # get the current joint's matrix information
    for jnt in jnts_same_level:
      jnt_name = jnt.attrib["name"]
      # create the bone
      bpy.ops.armature.bone_primitive_add(name = jnt_name)
      armature.data.edit_bones[jnt_name].length = 10
      mat = collada_funcs.get_text_mat4x4(jnt.find("matrix").text, 0)
      # check if this is the root joint of the skeleton
      if (is_first_jnt == False):
        parent_name = jnt.getparent().attrib["name"]
        # parent the bone
        armature.data.edit_bones[jnt_name].parent = armature.data.edit_bones[parent_name]
        # check if the current bone matrix is not zero filled
        if (mat.determinant() == 0):
          mat = math_funcs.get_id_mat4x4()
      # add the respective matrix to the dictionary and assign it to the bone
      rest_matrices.update({jnt_name : mat})
      
    jnts_same_level = next_jnts
    is_first_jnt = False
  
  # apply the bind matrices
  for i in range(0, len(armature.data.edit_bones)):
    bone = armature.data.edit_bones[i]
    if (bone.name in bind_matrices):
      bone.matrix = bind_matrices[bone.name]
    else:
      if (bone.parent != None):
        bone.matrix = bone.parent.matrix * rest_matrices[bone.name]
      else:
        bone.matrix = rest_matrices[bone.name]
  
  # get to pose mode
  bpy.ops.object.mode_set(mode='POSE')
  
  # set current pose to be rest pose and rest pose to be rest pose
  for bone in armature.pose.bones:
    if (bone.parent != None):
      bone.matrix = bone.parent.matrix * rest_matrices[bone.name]
    else:
      bone.matrix = rest_matrices[bone.name]
  
  # apply visual transform to all meshes
  bpy.ops.object.mode_set(mode='OBJECT')
  for child in armature.children:
    blender_funcs.select_obj(scene, child, False)
    bpy.ops.object.convert(target='MESH')
  
  # apply pose to rest pose
  blender_funcs.select_obj(scene, armature, False)
  bpy.ops.object.mode_set(mode='POSE')
  bpy.ops.pose.armature_apply()
  
  # reassign the armature modifiers to all meshes
  bpy.ops.object.mode_set(mode='OBJECT')
  for child in armature.children:
    blender_funcs.select_obj(scene, child, False)
    bpy.ops.object.modifier_add(type = 'ARMATURE')
    child.modifiers["Armature"].object = armature
    child.modifiers["Armature"].use_vertex_groups = True
  
  # scale down the model and apply transformations
  armature.scale = (0.01, 0.01, 0.01)
  blender_funcs.transf_apply_recurse(scene, armature, True, True, True)
  
  # done!
  blender_funcs.disp_msg("SuperBMD Collada file imported!")
  return {'FINISHED'} 


#################################################
# Stuff down is for the menu appending
# of the importer to work plus some setting stuff
# comes from a Blender importer template

# ExportHelper is a helper class, defines filename and
# invoke() function which calls the file selector.
from bpy_extras.io_utils import ExportHelper
from bpy.props import StringProperty, BoolProperty, EnumProperty
from bpy.types import Operator

class import_superbmd_collada(Operator, ExportHelper):
  """Import a Collada file from SuperBMD (SuperBMD only)"""
  bl_idname = "import_scene.superbmd_collada"
  bl_label = "Import SuperBMD Collada (.DAE)"

  # ExportHelper mixin class uses this
  filename_ext = ".dae"
  filter_glob = StringProperty(default = "*.dae", options = {'HIDDEN'}, maxlen = 255)
  # execute function
  def execute(self, context):
    return import_collada_superbmd(context, self.filepath)

# Only needed if you want to add into a dynamic menu
def menu_import_superbmd_collada(self, context):
  self.layout.operator(import_superbmd_collada.bl_idname, text="SuperBMD Collada (.dae)")

bpy.utils.register_class(import_superbmd_collada)
bpy.types.INFO_MT_file_import.append(menu_import_superbmd_collada)

# test call
bpy.ops.import_scene.superbmd_collada('INVOKE_DEFAULT')
